
#ifndef DIRECTORY_H
#define DIRECTORY_H

#include "FreeList.h"
#include <vector>
#include "DirectoryEntry.h"
#include "BlockLinkedList.h"
#include <string.h>
#include <math.h>

using namespace std;
/**
 *Class that represents the storage of directory entries
 */
class Directory {
 public:
  /**Populate directory from disk.
   *@param disk refers to the disk being read.
   *@param format if true the directory will be formatted
   *Read directory blocks on disk into directory object in RAM.
   */

  Directory(FreeList *freeList, bool format) {

      //finish this
      this->m_freeList = freeList;

      Block masterBlock = Block(0, m_freeList->getDisk());
      
      if(format)
      {
          this->m_entryList.clear();
          masterBlock.SetPointer(-1, 1);
          masterBlock.SetPointer(-1, 2);
          masterBlock.SetPointer(0, 3);
          masterBlock.Write(m_freeList->getDisk());
      }

      else
      {
         int dirBegAddr = masterBlock.GetPointer(1);
         int dirEndAddr = masterBlock.GetPointer(2);
         int dirLength = masterBlock.GetPointer(3);
          BlockGroup dir =
                  BlockGroup(dirBegAddr,dirEndAddr, dirLength, m_freeList);
          dir.Rewind();
          if(dir.GetCurrentBlock()->GetBlockNumber() == -1)
          {
              //opening an empty directory
              m_entryList.clear();
          }
          else
          {
              dir.Rewind();
              int numBlocks = dir.GetNumberOfBlocks();
              for(int i = 0; i < numBlocks; i++)
              {
                //Read the directories from the block
                  parseDirBlock(dir.GetCurrentBlock());
                  dir.GetNextBlock();
              }
          }
      }
      
}

/** Add a directory entry
 *@param newEntry refers to the entry to be added to the directory.
 *@return returns true on success
 * Takes a new directory entry and adds it to the end of the directory.
 */

 bool add(DirectoryEntry newEntry){
     Block masterBlock = Block(0, this->m_freeList->getDisk());
     int blocksUsed = masterBlock.GetPointer(3);
     int blocksNeeded = ((m_entryList.size()+1)/4) +1;
     int diff = blocksNeeded - blocksUsed;
     m_freeList->lock();
     if(this->m_freeList->GetNumberOfBlocks() < diff)
     {
         return false;
     }
     m_freeList->unlock();

     this->m_entryList.push_back(newEntry);
     char buf[123];
     char buf2[123];
     m_entryList.back().getFileName(buf);
     newEntry.getFileName(buf2);
     if(strcmp(buf, buf2) == 0)
     {
         return true;
     }
     else
         return false;
 }

 /** Rename a file
  *@param old is the name of the file to be changed
  *@param new is the name to change to
  *@return true on success
  *changes the name variable of the selected entry.
  */
 bool rename(char* oldName, char* newName){
     int index = findIndex(oldName);
     if(index >= 0)
     {
         this->m_entryList[index].setFileName(newName);
         return true;
     }
     else return false;
 }

 /** Remove an entry
  *@param name refers to the name of the file to be removed
  *@return returns true on success
  *call search to find the specified file name
  *remove selected entry from the directory
  *release the file's blocks to the freelist and its File control block
  */
 bool remove(char* name){
     int index = findIndex(name);
     if (index >= 0)
     {
         this->m_entryList.erase(m_entryList.begin() + index);
         return true;
     }
     else return false;
 }

 /** output the directory
  *outputs the whole directory including block numbers of every files master block
  *@return a vector pointer that points at the directory list
  *NOTE: The data returned is meant to be static and not altered
  */
 vector<DirectoryEntry>* list(){
   return &m_entryList;
 }
 /**write the directory to the disk
  */
 void flush()
 {
     //finish this
     /* Get the master block from the freelist
      * Rebuild the blockgroup as we did in the constructor.
      * Return all of these blocks to the freelist.
      * Get a NEW blockgroup big enough to contain all of our directory entries
      * iterate through vector populating each block in the blockgroup
      * with (up to) 4 entries
      * Rewind the blockgroup, and iterate through it, writing each block to disk
      */
     Block masterBlock = Block (0, m_freeList->getDisk());
     BlockGroup toReturn = BlockGroup(masterBlock.GetPointer(1),
                                         masterBlock.GetPointer(2),
                                         masterBlock.GetPointer(3),
                                         m_freeList);
     int free = masterBlock.GetPointer(0);
     int start = masterBlock.GetPointer(1);
     int end = masterBlock.GetPointer(2);
     int count = masterBlock.GetPointer(3);
	

	//lock
	m_freelist->lock();
     m_freeList->Return(&toReturn);
	m_freelist->unlock();

     //Old Directory has been returned to the Freelist, effectively deleting it on disk.

     if(m_entryList.empty()){
          masterBlock.SetPointer(-1, 1);
          masterBlock.SetPointer(-1, 2);
          masterBlock.SetPointer(0, 3);
          masterBlock.Write(m_freeList->getDisk());
     }
     else{
	m_freeList->lock();
        BlockGroup* newDir = m_freeList->CreateNew();
	m_freeList->unlock();
        masterBlock = Block(0, this->m_freeList->getDisk());
        //cout << "Freelist begins: " << masterBlock.GetPointer(0) << "\n";
        for(int i = 0; i< (m_entryList.size()-1)/4; i++)
        {
            //add enough blocks to hold our entries
	    m_freeList->lock();
            newDir->AddNewBlock();
	    m_freeList->unlock();
        }
        
        //write the directory metadata to the master block;
        masterBlock.SetPointer(newDir->GetStartBlockNumber(), 1);
        masterBlock.SetPointer(newDir->GetEndBlockNumber(), 2);
        masterBlock.SetPointer(newDir->GetNumberOfBlocks(), 3);
        masterBlock.Write(m_freeList->getDisk());
        Block master(0, m_freeList->getDisk());
        //cout<< "Directory starts at: " << master.GetPointer(1) << " ends at " << master.GetPointer(2) << " with length " << master.GetPointer(3) << "\n";

        //now add 4 directories to each block

        newDir->Rewind();
        int numBlocks = newDir->GetNumberOfBlocks();
        for(int i = 0; i < numBlocks; i++)
        {
            char blockBuffer[512];
            for(int entryIndex = i*4; entryIndex < (i+1)*4 ; entryIndex++)
            {
                int offset = ((entryIndex * 127) + (i+1)*4)%512;
                if(entryIndex < m_entryList.size()){
                    memcpy(&blockBuffer[offset], parseEntry(m_entryList[entryIndex]), 127);
                }
                else
                {
                    DirectoryEntry emptyDirEnt = DirectoryEntry("\0", -1);
                    memcpy(&blockBuffer[offset], parseEntry(emptyDirEnt), 127);
                }
            }
            int nextIndex = newDir->GetCurrentBlock()->GetPointer(0);
            newDir->GetCurrentBlock()->SetBuffer((unsigned char*)blockBuffer);
            newDir->GetCurrentBlock()->SetPointer(nextIndex, 0);
            //now write each block in the newDir blockgroup to the disk
            newDir->GetCurrentBlock()->Write(m_freeList->getDisk());
            //now go get the next block
            newDir->GetNextBlock();
        }
     }
 }

 /**
  *checks to see if fileName already exists in directory
  *@param fileName name of file being checked in the directory
  *@return returns true if file exists and false if it does not
  */
 bool exists(char* fileName){
     if(Search(fileName) > 0)
     {
         return true;
     }
     else
         return false;

 }

 
 /**
  * tests the Directory class
  */
 void Test()
 {

 }


void displayDirectory()
     {
         if(m_entryList.empty())
         {
             cout << "Directory is empty\n";
         }
         for (int i=0; i< this->m_entryList.size(); i++)
         {
             char fnamebuf[123];
             m_entryList[i].getFileName(fnamebuf);
             cout << "[Entry "<< i << "] Filename: "<< fnamebuf << " FCB: "<< m_entryList[i].getfcbNumber() << " \n";
         }

     }

FreeList* GetFreeList()
{
    return m_freeList;
}

/**
 * Searches the directory for the FCB number for DirectoryEntry with the given name
 * @param buf buffer holding the name of the file to find
 * @return the fcb number of the directoryEntry, or -1 if not found;
 */
 int Search(char* buf){
   for(int i=0; i < m_entryList.size(); i++){
     DirectoryEntry cur = m_entryList[i];
     char buffer[DirectoryEntry::MAX_NAME_LENGTH];
     cur.getFileName(buffer);
     if(0 == strcmp(buffer, buf)){
       return cur.getfcbNumber();
     }
   }
   return -1;
 }



 private:
     FreeList* m_freeList;
     vector<DirectoryEntry> m_entryList;





     void parseDirBlock(Block* block)
     {
         char* buf = (char*)block->GetBuffer();
        //read all directory entries from the block and add the to m_entryList;
         for(int i = 0; i < 4; i++)
         {
             int offset = (i*127) + 4;
             int *fcbPointer = (int*)&buf[offset];
             int fcbNumber = *fcbPointer;
             if(fcbNumber == -1) break;
             char fileName[123];
             memcpy(fileName, &buf[offset + 4], 123);
             DirectoryEntry dirEnt(fileName, fcbNumber);
             this->m_entryList.push_back(dirEnt);
         }
     }
     char* parseEntry(DirectoryEntry entry)
     {
         char buffer[127];
         int* fcbPoint = (int*)buffer;
         int fcb = entry.getfcbNumber();
         *fcbPoint = fcb;
         entry.getFileName(&buffer[4]);
         return buffer;

     }
     int findIndex(char* fileName)
     {
         for(int i = 0; i<m_entryList.size(); i++)
         {
             char buf[123];
             m_entryList[i].getFileName(buf);
             if(strcmp(buf, fileName) == 0)
             {
                 return i;
             }
         }
         return -1;
     }
};




#endif
